﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using JAAF.Collections;
using System.Diagnostics;
using System.Threading;
using JAAF.Builders;
using System.Reflection;

namespace JAAF.UI
{
    public abstract class UIApplication : IDisposable
    {
        #region Constants

        private const string m_sDEFAULT_VIEWSPACE_NAME = "JAAF.UI.DEFAULT_VIEWSPACE";

        #endregion


        #region Instance Data

        private Dictionary<string, IViewspace> m_dicViewspaces;

        private ReadOnlyDictionary<string, IViewspace> m_dicViewspacesRO;

        private UIProcess m_oRootProcess;

        private UIApplicationStatusEnum m_eStatus;

        /// <summary>
        /// The builder used for creating and initializing the views.
        /// </summary>
        private BuilderBase m_oViewBuilder;

        private IShell m_oShell;

        private bool m_bIsShellOpen;

        #endregion


        #region Initialization

        protected UIApplication()
        {
            m_dicViewspaces = new Dictionary<string, IViewspace>();
            m_dicViewspacesRO = m_dicViewspaces.AsReadonly();
        }

        #endregion


        #region Events

        /// <summary>
        /// An event that is fired when the application begins,
        /// just before the user interface shell is displayed
        /// and the root process is started.
        /// </summary>
        public event EventHandler Starting;

        /// <summary>
        /// This method is called by the <see cref="UIApplication.Run"/>
        /// method. It fires the <see cref="UIApplication+Starting"/>
        /// event.
        /// </summary>
        /// <remarks>
        /// Subclasses should place all initialization
        /// code in this method.
        /// </remarks>
        protected virtual void OnStarting()
        {
            if (this.Starting != null)
                this.Starting(this, EventArgs.Empty);

        }

        /// <summary>
        /// An event that is fired right after the application starts and before the shell is displayed.
        /// </summary>
        public event EventHandler Started;

        /// <summary>
        /// Fires the <see cref="UIApplication+Started"/> event.
        /// </summary>
        protected virtual void OnStarted()
        {
            if (this.Started != null)
                this.Started(this, EventArgs.Empty);
        }

        #endregion


        #region Properties

        /// <summary>
        /// Provides a title for the application
        /// </summary>
        public abstract string Title
        {
            get;
        }

        /// <summary>
        /// Gets the current <see cref="UIApplicationStatus">status</see> of this application.
        /// </summary>
        public UIApplicationStatusEnum Status
        {
            get
            {
                return m_eStatus;
            }
        }

        /// <summary>
        /// A dictionary with <see cref="IViewspace">viewspaces</see> of this application
        /// </summary>
        public ReadOnlyDictionary<string, IViewspace> Viewspaces
        {
            get
            {
                return m_dicViewspacesRO;
            }
        }

        /// <summary>
        /// Gets the default <see cref="IViewspace">viewspace</see> for this application
        /// </summary>
        public IViewspace DefaultViewspace
        {
            get
            {
                IViewspace oDefWorkspc = null;

                if (m_dicViewspaces.TryGetValue(m_sDEFAULT_VIEWSPACE_NAME, out oDefWorkspc))
                {
                    return oDefWorkspc;
                }
                else
                {
                    return m_dicViewspaces.Values.FirstOrDefault();
                }
            }
        }

        #endregion


        #region Methods

        /// <summary>
        /// Starts the application
        /// </summary>
        public void Run()
        {
            try
            {
                m_eStatus = UIApplicationStatusEnum.Starting;

                this.OnStarting();

                //
                // Perform user authentication
                //
                if (!this.AuthenticateUser())
                {
                    this.ShutDown();
                    return;
                }

                //
                // Create the builder for the application views
                //
                m_oViewBuilder = this.CreateViewBuilder();
                if (m_oViewBuilder == null)
                    throw new NullReferenceException("No builder implementation has been provided for creating the application views. The application will now terminate."); //RESOURCE

                //
                // Create and show the application shell
                //
                m_oShell = this.CreateShell();
                if (m_oShell == null)
                    throw new NullReferenceException("No shell has been provided for the application. The application will now terminate."); //RESOURCE

                this._RegisterShellEvents();

                m_oShell.Application = this;
                m_oShell.Open();
                m_bIsShellOpen = true;

                m_eStatus = UIApplicationStatusEnum.Running;

                this.OnStarted();

                //
                // Create and run the root user interface process
                //
                m_oRootProcess = this.CreateRootProcess();
                this._RegisterRootProcessEvents();
                m_oRootProcess.Start();

                
                m_oShell.Wait();
            }

            catch (Exception exc)
            {
                //TODO: Perform error logging
                this.DisplayException(exc);
                throw;
            }
        }

        /// <summary>
        /// This method must be overriden by concrete subclasses. It must create and initialize the application
        /// shell, register it's <see cref="IViewspace"/> workspaces and return it to the caller.
        /// </summary>
        protected abstract IShell CreateShell();

        /// <summary>
        /// This method must be overriden by concrete subclasses and provide an instance of the
        /// root <see cref="UIProcess">process</see> of the application.
        /// </summary>
        /// <returns>An instance of a <see cref="UIProcess"/>.</returns>
        /// <remarks>
        /// This is a factory method that should only instanciate and return the root user interface
        /// object. The starting of the root process will be performed <see cref="UIApplication.Run"/>
        /// method.
        /// </remarks>
        protected abstract UIProcess CreateRootProcess();

        /// <summary>
        /// This method must be overriden by concrete subclasses and provide an instance of
        /// <see cref="JAAF.Builers.BuilderBase"/> derived class that will be responsible for
        /// constructing and initializing the views for the application
        /// </summary>
        /// <returns>A <see cref="JAAF.Builders.BuilderBase"/> implementation.</returns>
        protected abstract BuilderBase CreateViewBuilder();

        /// <summary>
        /// Factory method for creating <see cref="IView">views</see> to be displayed
        /// by this <see cref="UIApplication"/>.
        /// </summary>
        /// <remarks>
        /// This method should allways be used for creating <see cref="IView">views</see>
        /// instead of instantiating them directly
        /// </remarks>
        /// <param name="_sViewName">The name of the view to create</param>
        /// <returns>An instance of the view.</returns>
        public IView CreateView(string _sViewName)
        {
            return (IView)m_oViewBuilder.Create(_sViewName);
        }

        /// <summary>
        /// Factory method for creating <see cref="IView">views</see> to be displayed
        /// by this <see cref="UIApplication"/>.
        /// </summary>
        /// <remarks>
        /// This method should allways be used for creating <see cref="IView">views</see>
        /// instead of instantiating them directly.
        /// </remarks>
        /// <param name="_sViewName">The type of the view to create</param>
        /// <returns>An instance of the view.</returns>
        public IView CreateView(Type _oViewType)
        {
            if (m_oViewBuilder.IsRegistered(_oViewType))
                return (IView)m_oViewBuilder.Create(_oViewType);
            else
                return (IView)Activator.CreateInstance(_oViewType);
        }

        /// <summary>
        /// This method is called by the <see cref="UIApplication.Run"/> method
        /// before user interface shell is displayed.
        /// </summary>
        /// <remarks>
        /// In it's default implementation, this method does not perform any authentication at all.
        /// Derived types must override this method to control the users' login to the application.
        /// </remarks>
        /// <returns>This method should return <see langword="true"/> if the login is
        /// successful. Returning <see langword="false"/> will cause the <see cref="UIApplication.ShutDown"/> method
        /// to be called and the application will terminate.</returns>
        protected virtual bool AuthenticateUser()
        {
            return true;
        }

        /// <summary>
        /// Decides if the application can stop at this moment
        /// </summary>
        /// <returns>True if the application is in a state that it can stop, otherwise false.</returns>
        public virtual bool CanShutDown()
        {
            if (m_oRootProcess == null
                || m_oRootProcess.IsStopped
                || m_oRootProcess.Status == UIProcessStatusEnum.Aborting
                || m_oRootProcess.Status == UIProcessStatusEnum.Completing)
            {
                return true;
            }

            if (!m_oRootProcess.CanAbort())
                return false;
            
            return true;
        }

        /// <summary>
        /// This method can be called to notify all the running <see cref="UIProcess">user interface processes</see>
        /// to abort and then shut down the application;
        /// </summary>
        public void ShutDown(bool _bForce = false)
        {
            if (m_eStatus == UIApplicationStatusEnum.ShuttingDown)
                return;

            if (!_bForce && !this.CanShutDown())
                return;

            m_eStatus = UIApplicationStatusEnum.ShuttingDown;

            try
            {
                if (m_oRootProcess != null)
                    m_oRootProcess.Abort();

                if (m_oShell != null && m_bIsShellOpen)
                    m_oShell.Close();

                m_eStatus = UIApplicationStatusEnum.Stopped;

            }
            catch
            { }

            ((IDisposable)this).Dispose();
        }


        private void _RegisterShellEvents()
        {
            m_oShell.Closing += this._OnShellClosingInternal;
        }

        private void _RegisterRootProcessEvents()
        {
            m_oRootProcess.Stopped += new EventHandler(this._OnRootProcessStopped);
        }

        private void _OnRootProcessStopped(object sender, EventArgs e)
        {
            if (this.Status != UIApplicationStatusEnum.ShuttingDown)
                m_oShell.Close();
        }

        /// <summary>
        /// Hanldes the closing event of the shell. Asks the root user interface process
        /// (which in turn asks all it's child processes) if it is OK to abort.
        /// The closing of the shell is allowed only if all the running processes agree.
        /// </summary>
        private void _OnShellClosingInternal(object _oSender, CancelEventArgs _oArgs)
        {
            this.OnShellClosing(_oSender, _oArgs);
            if (_oArgs.Cancel)
                return;

            if (!this.CanShutDown())
            {
                _oArgs.Cancel = true;
                return;
            }

            m_bIsShellOpen = false;
            this.ShutDown(true);
        }

        /// <summary>
        /// This method is called just before the shell of the application is closed.
        /// It gives the chance to derived classes to perform custom actions, or to cancel
        /// the closing of the shell by setting the <see cref="CancelEventargs.Cancel"/>
        /// property to true.
        /// </summary>
        /// <param name="_oArgs">A <see cref="CancelEventArgs"/> object.</param>
        protected virtual void OnShellClosing(object _oSender, CancelEventArgs _oArgs)
        {

        }

        /// <summary>
        /// Adds a <see cref="IViewspace">viewspace</see> to the application.
        /// </summary>
        /// <param name="_oViewspace">The <see cref="IWorkspace">viewspace</see> to add.</param>
        /// <param name="_sName">The name of the viewspace</param>
        public void RegisterViewSpace(IViewspace _oViewspace, string _sName = null)
        {
            if (_sName == null)
                _sName = m_sDEFAULT_VIEWSPACE_NAME;

            m_dicViewspaces.Add(_sName, _oViewspace);
        }

        protected bool UnregisterViewspace(string _sName = null)
        {
            if (_sName == null)
                _sName = m_sDEFAULT_VIEWSPACE_NAME;

            return m_dicViewspaces.Remove(_sName);
        }

        
        #region Notification dialog methods

        /// <summary>
        /// This method should be overriden by concrete subclasses and display a dialog
        /// with a question with two options, Yes an NO
        /// </summary>
        /// <param name="_sMessage">The question to display to the user</param>
        /// <param name="_sTitle">The title of the dialog</param>
        /// <param name="_bDefaultResponse">Defines the default response for the dialog.</param>
        /// <returns>True if the selected response is Yes and false if the response is No</returns>
        public abstract bool DisplayYesNoQuestion(string _sMessage, string _sTitle = null, bool _bDefaultResponse = true);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_sMessage"></param>
        /// <param name="_sTitle">The title of the dialogue</param>
        public abstract void DisplayErrorMessage(string _sMessage, string _sTitle = null);

        /// <summary>
        /// Displays an error message based on a given <see cref="System.Exception"/>.
        /// </summary>
        /// <param name="_exc">The exception to display.</param>
        /// <param name="_sTitle">The title of the dialogue</param>
        public abstract void DisplayException(Exception _exc, string _sTitle = null);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_sMessage"></param>
        /// <param name="_sTitle">The title of the dialogue</param>
        public abstract void DisplayInfo(string _sMessage, string _sTitle = null);
        
        #endregion

        #endregion


        #region IDisposable Members

        public void Dispose()
        {

        }

        #endregion
    }
}
